package cn.edu.zxj.ojspring.model.compile;

import cn.edu.zxj.ojspring.util.CommandUtils;
import cn.edu.zxj.ojspring.util.FileUtils;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
 * Created with IntelliJ IDEA.
 * Description：每次的 "编译+运行" 这个过程, 就称为是一个 compile.Task
 * 该方法通过 Question 请求, 经过编译和运行处理后, 返回一个 Answer 结果
 *
 * @author: zxj
 * @date: 2024-02-20
 * @time: 12:51:37
 */
@Data
@Component
@Slf4j
public class Task {

    // 约定临时文件名

    // 编译类名, 每一道题目的类名固定为 Solution -- 和 leetcode 上一样
    private static final String CLASS_NAME = "Solution";

    // 临时工作目录
    private String WORK_DIR;
    // 编译代码的文件名
    private String CODE;
    private String STDIN;
    private String STDOUT;
    private String STDERR;
    private String COMPILE_ERROR;

    public Task() {
        WORK_DIR = "./tmp/" + UUID.randomUUID().toString() + "/";
        CODE = WORK_DIR + "Solution.java";
        STDIN = WORK_DIR + "stdin.txt";
        STDOUT = WORK_DIR + "stdout.txt";
        STDERR = WORK_DIR + "stderr.txt";
        COMPILE_ERROR = WORK_DIR + "compile_error.txt";
    }


    /**
     * @description: Task的 核心方法, 处理编译的运行逻辑
     **/
    public Answer compileAndRun(Question question) {
        Answer answer = new Answer();
        // 0. 准备好存放临时文件的目录
        File wordDir = new File(WORK_DIR);
        if (!wordDir.exists()) {
            // 目录不存在, 创建
            boolean falg = wordDir.mkdirs();
            if (falg) {
                log.info("创建临时目录成功~");
            } else {
                log.info("创建临时目录失败~");
            }
        }
        // 安全性检查
        if (!userCodeIsSafe(question.getCode())) {
            answer.setError(3);
            answer.setErrorMessage("您提供的代码不安全, 请重新编写");
            return answer;
        }


        // 1. 根据 question 创建临时文件
        FileUtils.writeContentToFile(CODE, question.getCode());
        FileUtils.writeContentToFile(STDIN, question.getStdin());
        // 2. 构建编译命令, 并执行
        // 2.1 构造编译指令 javac -d 用来指定生成的 .class 文件的位置.
        String compileCmd = String.format(
                "javac -encoding utf8 %s -d %s",
                CODE, WORK_DIR
        );
        log.info("编译指令: {}", compileCmd);
        // 2.2 创建新的进程, 来执行编译进程
        CommandUtils.run(compileCmd, null, COMPILE_ERROR);
        // 2.3 判断是否编译通过
        String compileError = FileUtils.readFile(COMPILE_ERROR);
        if (StringUtils.hasLength(compileError)) {
            // 错误文件里面有内容, 则说明编译出错
            log.info("编译出现错误~");
            answer.setError(1);
            answer.setErrorMessage(compileError);
            return answer;
        }
        // 3. 构建运行命令, 并执行
        // 3.1 构造运行指令
        String runCmd = String.format(
                "java -classpath %s %s",
                WORK_DIR, CLASS_NAME
        );
        log.info("运行指令: {}", runCmd);
        // 3.2 执行
        CommandUtils.run(runCmd, STDOUT, STDERR);
        // 3.3 判断运行的时候是否发生了异常
        String runError = FileUtils.readFile(STDERR);
        if (StringUtils.hasLength(runError)) {
            log.info("运行出现错误~");
            answer.setError(2);
            answer.setErrorMessage(runError);
            return answer;
        }
        // 4. 将运行结果包装成一个 Answer 并返回
        // 编译和运行都成功了
        String stdoutMessage = FileUtils.readFile(STDOUT);
        answer.setError(0);
        answer.setStdoutMessage(stdoutMessage);
        return answer;
    }

    private boolean userCodeIsSafe(String code) {
        List<String> blackKeyWord = new ArrayList<>();
        blackKeyWord.add("Runtime");
        blackKeyWord.add("exec");
        blackKeyWord.add("java.io");
        blackKeyWord.add("java.net");
        for (String word : blackKeyWord) {
            int pos = code.indexOf(word);
            if (pos >= 0) {
                return false;
            }
        }
        return true;
    }
}
